package com.chenl.provider.DEMOS.日志相关.全局日志;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.arms.tracing.Span;
import com.alibaba.arms.tracing.Tracer;
import com.alibaba.fastjson.JSONObject;
import com.chenl.provider.util.RedisService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;

//@Component
@Aspect
@Order(1)
public class WebLogAspect {
    ThreadFactory springThreadFactory = new CustomizableThreadFactory("统计线程池-");
    ExecutorService executorService = new ThreadPoolExecutor(50, 200,
            1L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(),springThreadFactory);
    protected final Logger log = LoggerFactory.getLogger(this.getClass());



    @Value("${openReturn.flag:1}")
    private String openReturn;

    @Value("${req.statistics:dct}")
    private String center;

    @Value("${req.statistics.name:集成网关}")
    private String centerName;

    @Autowired
    RedisService redisService;

    public WebLogAspect() {
    }

    @Pointcut("execution(* com.chenl..*Con.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Span span = Tracer.builder().getSpan();
        String traceId = span.getTraceId();
        MDC.put("traceId", traceId);
        MDC.put("centerName", center.replaceAll("/","").toUpperCase());
        this.log.info("==================请求开始==================");
        this.log.info("[请求IP]:{}", request.getRemoteAddr());
        this.log.info("[请求URL]:{}", request.getRequestURL());
        this.log.info("[请求方式]:{}", request.getMethod());
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String header = headerNames.nextElement();
            this.log.info("[请求头]:{}",header+":"+request.getHeader(header));
        }
        this.log.info("[请求traceId]:{}", traceId);
        this.log.info("[请求类名]:{},[请求方法名]:{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        this.log.info("[请求参数]:{}", JSONObject.toJSONString(this.getParameter(method, joinPoint.getArgs())));
        //统计访问次数(异步)  hash结构  ReqCount_日期_centerName:请求方式_接口路径_IP:数量
        executorService.execute(()->{
            reqCountStat(request,method);
        });
        if (StrUtil.isEmpty(traceId) && StrUtil.isEmpty(request.getHeader("X-B3-TraceId"))) {
            traceId = IdUtil.fastSimpleUUID();
        }
//        ExtraParamUtil.getExtraParam().setTraceId(traceId);
        Object[] args = joinPoint.getArgs();
        Object retVal;
        try {
            retVal = joinPoint.proceed(args);
        } catch (Exception var17) {
            this.log.info("==================请求发生异常==================");
            this.log.error(var17.getMessage(), var17);
            // 获取异常的堆栈跟踪信息
            StackTraceElement[] stackTraceElements = var17.getStackTrace();

            // 获取异常发生的类名和行号
            if (stackTraceElements.length > 0) {
                StackTraceElement firstElement = stackTraceElements[0];
                String className = firstElement.getClassName();
                int lineNumber = firstElement.getLineNumber();

                System.err.printf("Exception occurred in class %s at line %d%n", className, lineNumber);
            }
            long endTime = System.currentTimeMillis();
            this.log.info("[请求耗时]:{}秒", BigDecimal.valueOf(endTime).subtract(BigDecimal.valueOf(startTime)).divide(BigDecimal.valueOf(1000)));
            this.log.info("==================请求结束==================");
            throw var17;
        }
        long endTime = System.currentTimeMillis();
        this.log.info("[请求耗时]:{}秒", BigDecimal.valueOf(endTime).subtract(BigDecimal.valueOf(startTime)).divide(BigDecimal.valueOf(1000)));
        return retVal;
    }


    private void reqCountStat(HttpServletRequest request, Method method) {
        try {
            Long appId = 666L;
            Class<?> methodDeclaringClass = method.getDeclaringClass();
            Class<?>[] interfaces = methodDeclaringClass.getInterfaces();
            Method interfaceMethod = interfaces[0].getMethod(method.getName(), method.getParameterTypes());
            List<Parameter> parameters = Arrays.asList(method.getParameters());
            ApiOperation[] apiOperations = interfaceMethod.getAnnotationsByType(ApiOperation.class);
            if (apiOperations != null && apiOperations.length > 0 && StringUtils.isNotBlank(apiOperations[0].value()) && StringUtils.isNotBlank(apiOperations[0].tags()[0])) {
                String value = apiOperations[0].value();
                String tag = apiOperations[0].tags()[0];
                String reqCountKey = "ReqCount_" + center;
                String url = request.getRequestURI();
                //请求校验是否存在路径参数,若存在则获取到并手动拼接
                int count = 0;
                boolean flag=true;
                for (Parameter parameter : parameters) {
                    Annotation[] annotations = parameter.getAnnotations();
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof PathVariable) {
                            count++;
                        }
                    }
                }
                if (url.endsWith("/")){
                    count++;
                }
                for (Parameter parameter : parameters) {
                    Annotation[] annotations = parameter.getAnnotations();
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof PathVariable) {
                            PathVariable pathVariable = (PathVariable) annotation;
                            String s = pathVariable.value();
                            int lastIndex = findLastIndexof(url, '/', count);
                            if (flag){
                                flag=false;
                                url=url.substring(0,lastIndex);
                                url+="/{" + s + "}";
                            }else {
                                url+="/{" + s + "}";
                            }
                        }
                    }
                }
                //时间_请求方式_接口URL_接口说明_tag_appId_中心名称
                String reqCountHashKey =
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
                                + "_" + request.getMethod()
                                + "_" + (url.length()>150?url.substring(0,150):url)
                                + "_" + value
                                + "_" + tag
                                + "_" + (null != appId ? appId : "未知")
                                + "_" + centerName;
                redisService.hmIncr(reqCountKey, reqCountHashKey, 1L);
            }
        } catch (Exception e) {
            log.info("reqCountStat异常:" + e.toString());
        }
    }

    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; ++i) {
            RequestBody requestBody = (RequestBody) parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            RequestParam requestParam = (RequestParam) parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
            if (StringUtils.isNotBlank(openReturn) && openReturn.equals("1") && ret != null) {
                String s = JSONObject.toJSONString(ret);
                this.log.info("==================返回内容==================");
                this.log.info("RESPONSE:{}|{}|{}", (StringUtils.isNotBlank(s) && s.length() > 1500) ? s.substring(0, 1500) : s, "tempTraceId","tempAppId");
            }
            this.log.info("==================请求结束==================");
    }

    /**
     * 查找字符target在字符串text中倒数第n次出现的位置
     * @param text
     * @param target
     * @param n
     * @return
     */
    public static int findLastIndexof(String text, char target, int n) {
        int count = 0;
        int lastIndex = -1;
        for (int i = text.length() - 1; i >= 0; i--) {
            if (text.charAt(i) == target) {
                count++;
                if (count == n) {
                    lastIndex = i;
                    break;
                }
            }
        }
        return lastIndex;
    }
}


